/*
 * Copyright (C) 2020 Agrui
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
#include <stdio.h>
#include "ParameterCheck.h"
#include "OSMemory.h"
#include "IpcService.h"
#include "IpcChannelManager.h"
#include "IpcServiceManager.h"

#define MAX_CLIENT_COUNT        5

int8_t IpcServiceAllocClient(IpcService* service, int8_t channelId, int8_t subId) {
    PARAMTER_CHECK_RETURN_VALUE(service && service->clientList, -1);
    if (arrayListSize(service->clientList) > service->maxClientCount) {
        return -1;
    }

    IpcServiceClientData* clientData = (IpcServiceClientData*)OS_MALLOC(service->clientDataSize);
    static int key = 0;
    clientData->channelId = channelId;
    clientData->subId = subId;
    arrayListAddKey(service->clientList, key, clientData);
    if (service->ops.initClientCb) {
        service->ops.initClientCb(service, clientData);
    }
    return key++;
}

static boolean compareChannel(ArrayList* list, void* itemData, void* userData) {
    IpcServiceClientData* clientData = (IpcServiceClientData*)itemData;
    int8_t channelId = *(int8_t*)userData;
    if (clientData && clientData->channelId == channelId) {
        return TRUE;
    }
    return FALSE;
}

void IpcServiceFreeClient(IpcService* service, int8_t channelId, int8_t clientId) {
    PARAMTER_CHECK_RETURN_VOID(service && service->clientList);
    if (clientId == -1) {
        arrayListRemoveItems(service->clientList, compareChannel, OSFree, (void*)&channelId);
    } else {
        IpcServiceClientData* clientData = (IpcServiceClientData*)arrayListRemoveKey(service->clientList, clientId);
        if (clientData) {
            OS_FREE(clientData);
        }
    }
}

IpcService* newIpcService(int serviceId, ipcUnpackBuffer unpackCb, ipcFreeUnpackBuffer freeUnpackCb) {
    IpcService* service = OS_ALLOC_AND_RESET(IpcService);
    PARAMTER_CHECK_RETURN_NULL(service);
    service->serviceId = serviceId;
    service->ops.unpackCb = unpackCb;
    service->ops.freeUnpackCb = freeUnpackCb;
    service->clientDataSize = sizeof(IpcServiceClientData);
    service->maxClientCount = MAX_CLIENT_COUNT;
    service->clientList = newArrayList();
    if (!service->clientList) {
        OS_FREE(service);
        return NULL;
    }
    return service;
}

static void deleteClientData(void* itemData) {
    PARAMTER_CHECK_RETURN_VOID(itemData);
    OS_FREE(itemData);
}

void deleteIpcService(IpcService* service) {
    if (service) {
        if (service->clientList) {
            arrayListRemoveAll(service->clientList, deleteClientData);
            deleteArrayList(service->clientList);
        }
    }
}

IPC_RESULT ipcSendResponse(IpcTransaction* transaction, IPC_RESULT result, void* data, int dataLen) {
    return sendIpcResponse(transaction, result, data, dataLen);
}

IPC_RESULT ipcSendIndication(IpcService* service, int channelId, int msgId, void* data, int dataLen) {
    return sendIpcIndication(service, channelId, msgId, data, dataLen);
}


IPC_RESULT dispatchRequest(IpcService* service, IpcTransaction* transaction, void* data, int dataLen) {
    IPC_RESULT result = IPC_RESULT_OK;
    boolean found = FALSE;
    if (!service) {
        return IPC_RESULT_ERROR;
    }

    for (int i=0; i<service->dispatchTableCount; i++) {
        if (service->dispatchTable[i].msgId == transaction->msgId && service->dispatchTable[i].callback) {
            void* unpackData = NULL;
            if (!data && dataLen > 0 && service->ops.unpackCb) {
                unpackData = service->ops.unpackCb(NULL, dataLen, data);
            }

            void* clientData = arrayListGetKeyData(service->clientList, transaction->clientId);
            result = service->dispatchTable[i].callback(transaction, clientData, transaction->msgId, unpackData ? unpackData : data, dataLen);
            if (service->ops.freeUnpackCb && unpackData) {
                service->ops.freeUnpackCb(unpackData, NULL);
            }
            found = TRUE;
            break;
        }
    }

    if (!found) {
        return IPC_RESULT_NOT_SUPPORT;
    }

    return IPC_RESULT_OK;
}

void registerDispatchTable(IpcService* service, IpcServiceDispatcher* dispatcher, int count) {
    PARAMTER_CHECK_RETURN_VOID(service && dispatcher && count > 0);
    service->dispatchTable = dispatcher;
    service->dispatchTableCount = count;
}

